import cv2
import numpy as np
import pyautogui
import pyaudio
import wave
import threading
import tkinter as tk
from screeninfo import get_monitors
from tkinter import messagebox, filedialog
from scipy.signal import butter, lfilter
import os
import subprocess
import win32gui
import win32con
import win32api
import time
from tkinter import ttk
from PIL import Image, ImageTk
import zipfile
import sys
import logging
import json

# 获取程序所在目录
if getattr(sys, 'frozen', False):
    base_dir = os.path.dirname(sys.executable)
    logging.info(f"运行于打包环境,程序目录: {base_dir}")
    ffmpeg_zip = os.path.join(sys._MEIPASS, 'ffmpeg.zip')
    logging.info(f"ffmpeg.zip路径: {ffmpeg_zip}")
else:
    base_dir = os.path.dirname(os.path.abspath(__file__))
    logging.info(f"运行于开发环境,程序目录: {base_dir}")
    ffmpeg_zip = os.path.join(base_dir, 'ffmpeg.zip')
    logging.info(f"ffmpeg.zip路径: {ffmpeg_zip}")

# 配置文件路径
config_file = os.path.join(base_dir, 'config.json')

# 默认配置
default_config = {
    'audio': {
        'cutoff': 2000,  # 截止频率
        'order': 4,  # 滤波器阶数
        'channels': 1,
        'rate': 44100,
        'chunk': 1024,
        'format': 'wav'
    },
    'video': {
        'format': 'mp4',
        'fps': 20,
        'codec': 'mp4v'
    },
    'gui': {
        'title': 'LEMON屏幕录制工具',
        'start_text': '开始录制',
        'stop_text': '停止录制',
        'pause_text': '暂停',
        'resume_text': '继续',
        'ready_text': '就绪',
        'recording_text': '正在录制...',
        'paused_text': '已暂停',
        'processing_text': '正在处理文件...',
        'merging_text': '正在合并音视频...',
        'complete_text': '录制完成',
        'merge_failed_text': '合并失败',
        'canceled_text': '已取消保存'
    }
}

# 加载或创建配置文件
def load_config():
    try:
        if not os.path.exists(config_file):
            os.makedirs(os.path.dirname(config_file), exist_ok=True)
            with open(config_file, 'w', encoding='utf-8') as f:
                json.dump(default_config, f, ensure_ascii=False, indent=4)
            return default_config
        else:
            with open(config_file, 'r', encoding='utf-8') as f:
                return json.load(f)
    except Exception as e:
        logging.error(f"加载配置文件失败: {str(e)}")
        return default_config

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(os.path.join(base_dir, 'luzhi.log'), encoding='utf-8'),
        logging.StreamHandler()
    ]
)

# 设置ffmpeg环境变量
def setup_ffmpeg():
    try:
        if not os.path.exists(ffmpeg_zip):
            logging.error("找不到ffmpeg.zip文件")
            return False

        try:
            ffmpeg_dir = os.path.join(base_dir, "ffmpeg")
            if not os.path.exists(ffmpeg_dir):
                logging.info("开始解压ffmpeg.zip...")
                with zipfile.ZipFile(ffmpeg_zip, 'r') as zip_ref:
                    zip_ref.extractall(base_dir)
                logging.info("ffmpeg.zip解压完成")
        except Exception as e:
            logging.error(f"解压ffmpeg.zip失败: {str(e)}")
            return False

        bin_dir = os.path.join(base_dir, "ffmpeg", "bin")
        if not os.path.exists(bin_dir):
            logging.error("找不到ffmpeg的bin目录")
            return False
        
        current_path = os.environ.get('PATH', '')
        if bin_dir not in current_path:
            os.environ['PATH'] = f"{bin_dir};{current_path}"
            logging.info(f"已更新当前进程环境变量,PATH={os.environ['PATH']}")

        try:
            subprocess.run(['ffmpeg', '-version'], capture_output=True)
            logging.info("ffmpeg设置成功")
            return True
        except Exception as e:
            logging.error(f"ffmpeg设置失败: {str(e)}")
            return False

    except Exception as e:
        logging.error(f"设置环境变量失败: {str(e)}")
        return False

# 加载配置
config = load_config()

# 获取屏幕分辨率
screen_width, screen_height = pyautogui.size()

# 录制区域(默认全屏)
recording_area = (0, 0, screen_width, screen_height)

# 视频设置
fourcc = cv2.VideoWriter_fourcc(*config['video']['codec'])
out = None

# 音频设置
audio_format = pyaudio.paInt16
channels = config['audio']['channels']
rate = config['audio']['rate']
chunk = config['audio']['chunk']
audio_output = os.path.join(base_dir, f"output.{config['audio']['format']}")
video_output = os.path.join(base_dir, f"output.{config['video']['format']}")
final_output = os.path.join(base_dir, f"final_output.{config['video']['format']}")

# 降噪过滤器参数
cutoff = config['audio']['cutoff']  # 截止频率
nyquist = rate/2
order = config['audio']['order']  # 滤波器阶数

# 初始化 PyAudio
audio = pyaudio.PyAudio()

# 录制控制变量
recording = False
paused = False
selecting_area = False
start_pos = None
audio_thread = None
screen_thread = None
stop_event = None
area_window = None
drag_start = None
resize_edge = None
selection_rect = None
move_timer = None
last_move_time = 0
stopping = False

def butter_lowpass(cutoff, nyquist, order=4):
    normal_cutoff = cutoff / nyquist
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return b, a

def butter_lowpass_filter(data, cutoff, nyquist, order=4):
    b, a = butter_lowpass(cutoff, nyquist, order=order)
    y = lfilter(b, a, data)
    return y

def on_mouse_press(event):
    global start_pos, selecting_area, selection_rect
    if selecting_area:
        start_pos = (event.x, event.y)
        if selection_rect:
            canvas.delete(selection_rect)
        selection_rect = None

def on_mouse_motion(event):
    global selection_rect, start_pos
    if selecting_area and start_pos:
        if selection_rect:
            canvas.delete(selection_rect)
        
        x1, y1 = start_pos
        x2, y2 = event.x, event.y
        selection_rect = canvas.create_rectangle(x1, y1, x2, y2, 
                                               outline='red', 
                                               width=4,
                                               fill='gray',
                                               stipple='gray50')

def check_center_hover(event):
    width = area_window.winfo_width()
    height = area_window.winfo_height()
    center_x = width // 2
    center_y = height // 2
    
    # 检查鼠标是否在中心点附近(±10像素范围内)
    if abs(event.x - center_x) <= 10 and abs(event.y - center_y) <= 10:
        area_window.configure(cursor='fleur')
        return True
    return False

def on_area_mouse_press(event):
    global drag_start, resize_edge, last_move_time
    drag_start = (event.x_root, event.y_root)
    last_move_time = time.time()
    
    width = area_window.winfo_width()
    height = area_window.winfo_height()
    center_x = width // 2
    center_y = height // 2
    
    # 如果在中心点附近按下,等待1秒后才能移动
    if abs(event.x - center_x) <= 10 and abs(event.y - center_y) <= 10:
        resize_edge = None
        area_window.configure(cursor='fleur')
        return
        
    # 检测是否在边缘或角落
    if event.x < 10 and event.y < 10:
        resize_edge = 'topleft'
        area_window.configure(cursor='sizing')
    elif event.x > width - 10 and event.y < 10:
        resize_edge = 'topright'
        area_window.configure(cursor='sizing')
    elif event.x < 10 and event.y > height - 10:
        resize_edge = 'bottomleft'
        area_window.configure(cursor='sizing')
    elif event.x > width - 10 and event.y > height - 10:
        resize_edge = 'bottomright'
        area_window.configure(cursor='sizing')
    elif event.x < 10:
        resize_edge = 'left'
        area_window.configure(cursor='size_we')
    elif event.x > width - 10:
        resize_edge = 'right'
        area_window.configure(cursor='size_we')
    elif event.y < 10:
        resize_edge = 'top'
        area_window.configure(cursor='size_ns')
    elif event.y > height - 10:
        resize_edge = 'bottom'
        area_window.configure(cursor='size_ns')
    else:
        resize_edge = None
        area_window.configure(cursor='arrow')

def on_area_mouse_motion(event):
    global recording_area, out, drag_start, last_move_time
    
    if not drag_start:
        # 更新鼠标样式
        width = area_window.winfo_width()
        height = area_window.winfo_height()
        center_x = width // 2
        center_y = height // 2
        
        # 检查是否在中心点附近
        if abs(event.x - center_x) <= 10 and abs(event.y - center_y) <= 10:
            area_window.configure(cursor='fleur')
            return
            
        if event.x < 10 and event.y < 10:
            area_window.configure(cursor='sizing')
        elif event.x > width - 10 and event.y < 10:
            area_window.configure(cursor='sizing')
        elif event.x < 10 and event.y > height - 10:
            area_window.configure(cursor='sizing')
        elif event.x > width - 10 and event.y > height - 10:
            area_window.configure(cursor='sizing')
        elif event.x < 10:
            area_window.configure(cursor='size_we')
        elif event.x > width - 10:
            area_window.configure(cursor='size_we')
        elif event.y < 10:
            area_window.configure(cursor='size_ns')
        elif event.y > height - 10:
            area_window.configure(cursor='size_ns')
        else:
            area_window.configure(cursor='arrow')
        return
    
    # 检查是否已经等待了1秒
    if time.time() - last_move_time < 1:
        return
        
    try:    
        dx = event.x_root - drag_start[0]
        dy = event.y_root - drag_start[1]
        
        x = area_window.winfo_x()
        y = area_window.winfo_y()
        width = area_window.winfo_width()
        height = area_window.winfo_height()
        
        if resize_edge:
            # 调整大小
            if 'left' in resize_edge:
                new_x = x + dx
                new_width = width - dx
                if new_width > 100:
                    x = new_x
                    width = new_width
            if 'right' in resize_edge:
                new_width = width + dx
                if new_width > 100:
                    width = new_width
            if 'top' in resize_edge:
                new_y = y + dy
                new_height = height - dy
                if new_height > 100:
                    y = new_y
                    height = new_height
            if 'bottom' in resize_edge:
                new_height = height + dy
                if new_height > 100:
                    height = new_height
                    
            area_window.geometry(f"{width}x{height}+{x}+{y}")
        else:
            # 移动窗口
            x = x + dx
            y = y + dy
            area_window.geometry(f"+{x}+{y}")
        
        # 更新录制区域
        recording_area = (x, y, width, height)
        
        drag_start = (event.x_root, event.y_root)
    except Exception as e:
        logging.error(f"拖动区域出错: {str(e)}")

def on_area_mouse_release(event):
    global drag_start, resize_edge
    drag_start = None
    resize_edge = None

def on_mouse_release(event):
    global recording_area, selecting_area, out, area_window, selection_rect
    if selecting_area and start_pos:
        x1, y1 = start_pos
        x2, y2 = event.x, event.y
        
        left = min(x1, x2)
        top = min(y1, y2)
        right = max(x1, x2)
        bottom = max(y1, y2)
        
        width = right - left
        height = bottom - top
        if width < 100:
            right = left + 100
            width = 100
        if height < 100:
            bottom = top + 100
            height = 100
            
        recording_area = (left, top, width, height)
        
        # 创建一个透明的遮罩窗口
        area_window = tk.Toplevel()
        area_window.overrideredirect(True)
        area_window.attributes('-alpha', 0.3, '-topmost', True, '-transparentcolor', 'black')
        area_window.configure(bg='black')
        area_window.geometry(f"{width}x{height}+{left}+{top}")
        
        # 设置窗口样式使其可以点击穿透
        hwnd = win32gui.GetParent(area_window.winfo_id())
        style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
        style = style | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT
        win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, style)
        
        # 创建一个红色边框
        border = tk.Canvas(area_window, bg='black', highlightthickness=2, highlightbackground='red')
        border.place(x=0, y=0, relwidth=1, relheight=1)
        
        # 在中心绘制十字光标
        center_x = width // 2
        center_y = height // 2
        border.create_line(center_x-10, center_y, center_x+10, center_y, fill='red', width=2)
        border.create_line(center_x, center_y-10, center_x, center_y+10, fill='red', width=2)
        
        area_window.bind('<Button-1>', on_area_mouse_press)
        area_window.bind('<B1-Motion>', on_area_mouse_motion)
        area_window.bind('<ButtonRelease-1>', on_area_mouse_release)
        area_window.bind('<Motion>', on_area_mouse_motion)
        
        selecting_area.destroy()
        selecting_area = False
        start_recording_after_selection()

def select_area():
    global selecting_area, canvas
    root.withdraw()  # 使用withdraw()代替iconify()
    
    selecting_area = tk.Toplevel()
    selecting_area.overrideredirect(True)
    selecting_area.attributes('-alpha', 0.5, '-topmost', True)
    selecting_area.geometry(f"{screen_width}x{screen_height}+0+0")
    selecting_area.configure(cursor="cross", bg='black')
    
    canvas = tk.Canvas(selecting_area, bg='black')
    canvas.pack(fill=tk.BOTH, expand=True)
    
    canvas.bind('<Button-1>', on_mouse_press)
    canvas.bind('<B1-Motion>', on_mouse_motion)
    canvas.bind('<ButtonRelease-1>', on_mouse_release)
    
    label = tk.Label(selecting_area, text="请拖动鼠标选择录制区域", font=('Arial', 16), fg='white', bg='black')
    label.pack(pady=20)

def start_recording_after_selection():
    global recording, paused, out, audio_thread, screen_thread, stop_event
    
    try:
        if out is None:
            out = cv2.VideoWriter(video_output, fourcc, config['video']['fps'], (recording_area[2], recording_area[3]))
            logging.info(f"创建视频写入器成功,输出文件:{video_output}")
    except Exception as e:
        logging.error(f"创建视频写入器出错: {str(e)}")
        messagebox.showerror("错误", "创建视频写入器失败,请重试")
        return
        
    recording = True
    paused = False
    root.deiconify()
    
    start_button.config(state=tk.DISABLED)
    stop_button.config(state=tk.NORMAL)
    pause_button.config(state=tk.NORMAL)
    
    stop_event = threading.Event()
    
    audio_thread = threading.Thread(target=record_audio, args=(stop_event,))
    audio_thread.daemon = True
    audio_thread.start()
    
    screen_thread = threading.Thread(target=record_screen, args=(stop_event,))
    screen_thread.daemon = True
    screen_thread.start()
    
    status_label.config(text=config['gui']['recording_text'])
    logging.info("开始录制")

def record_audio(stop_event):
    try:
        stream = audio.open(format=audio_format, channels=channels,
                          rate=rate, input=True,
                          frames_per_buffer=chunk)
        frames = []

        while not stop_event.is_set() and recording:
            if not paused:
                data = stream.read(chunk)
                audio_data = np.frombuffer(data, dtype=np.int16)
                filtered_data = butter_lowpass_filter(audio_data, cutoff, nyquist, order)
                filtered_bytes = filtered_data.astype(np.int16).tobytes()
                frames.append(filtered_bytes)
            else:
                while paused and recording and not stop_event.is_set():
                    stop_event.wait(0.1)

        stream.stop_stream()
        stream.close()

        if frames:
            wf = wave.open(audio_output, 'wb')
            wf.setnchannels(channels)
            wf.setsampwidth(audio.get_sample_size(audio_format))
            wf.setframerate(rate)
            wf.writeframes(b''.join(frames))
            wf.close()
            logging.info(f"音频录制完成,输出文件:{audio_output}")
    except Exception as e:
        logging.error(f"音频录制出错: {str(e)}")
        stop_recording()

def start_recording():
    select_area()

def stop_recording():
    global recording, audio_thread, screen_thread, stop_event, area_window, out, stopping
    
    if stopping:
        return
        
    stopping = True
    logging.info("停止录制")
    
    if stop_event:
        stop_event.set()
    
    recording = False
    start_button.config(state=tk.NORMAL)
    stop_button.config(state=tk.DISABLED)
    pause_button.config(state=tk.DISABLED)
    
    if area_window:
        area_window.destroy()
        area_window = None
    
    # 等待线程结束,设置超时时间
    if audio_thread and audio_thread.is_alive() and audio_thread != threading.current_thread():
        audio_thread.join(timeout=2)
    if screen_thread and screen_thread.is_alive() and screen_thread != threading.current_thread():
        screen_thread.join(timeout=2)
        
    if out:
        try:
            out.release()
            logging.info("视频写入器已关闭")
        except Exception as e:
            logging.error(f"关闭视频写入器出错: {str(e)}")
        out = None
    cv2.destroyAllWindows()
    
    status_label.config(text=config['gui']['processing_text'])
    root.update()
    
    # 选择保存位置
    save_path = filedialog.asksaveasfilename(
        defaultextension=f".{config['video']['format']}",
        filetypes=[(f"{config['video']['format'].upper()} files", f"*.{config['video']['format']}")],
        title="选择保存位置"
    )
    
    if save_path:
        try:
            if os.path.exists(video_output) and os.path.exists(audio_output):
                status_label.config(text=config['gui']['merging_text'])
                root.update()
                logging.info("开始合并音视频")
                
                cmd = f'ffmpeg -y -i {video_output} -i {audio_output} -c:v copy -c:a aac "{save_path}"'
                subprocess.call(cmd, shell=True)
                os.remove(video_output)
                os.remove(audio_output)
                status_label.config(text=config['gui']['complete_text'])
                logging.info(f"合并完成,最终输出文件:{save_path}")
                messagebox.showinfo("完成", f"录制完成\n文件已保存至: {save_path}")
        except Exception as e:
            logging.error(f"合并音视频出错: {str(e)}")
            status_label.config(text=config['gui']['merge_failed_text'])
            messagebox.showerror("错误", "合并音视频失败")
    else:
        status_label.config(text=config['gui']['canceled_text'])
        logging.info("用户取消保存")
        # 清理临时文件
        if os.path.exists(video_output):
            os.remove(video_output)
        if os.path.exists(audio_output):
            os.remove(audio_output)
            
    stopping = False

def pause_recording():
    global paused
    paused = True
    pause_button.config(text=config['gui']['resume_text'], command=resume_recording)
    status_label.config(text=config['gui']['paused_text'])
    logging.info("录制已暂停")

def resume_recording():
    global paused
    paused = False
    pause_button.config(text=config['gui']['pause_text'], command=pause_recording)
    status_label.config(text=config['gui']['recording_text'])
    logging.info("录制已继续")

def record_screen(stop_event):
    try:
        while recording and not stop_event.is_set():
            if not paused:
                try:
                    img = pyautogui.screenshot(region=recording_area)
                    frame = np.array(img)
                    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
                    if out and out.isOpened():
                        out.write(frame)
                except Exception as e:
                    logging.error(f"写入视频帧出错: {str(e)}")
                    break
            else:
                stop_event.wait(0.1)
                
    except Exception as e:
        logging.error(f"录制出错: {str(e)}")
    finally:
        if not stopping:
            stop_recording()

# 设置ffmpeg环境变量
setup_ffmpeg()

# 创建GUI界面
root = tk.Tk()
root.title(config['gui']['title'])
root.geometry("600x200")  # 调整窗口宽度以适应横排按钮
root.configure(bg='#2C3E50')  # 深蓝色背景

# 设置窗口图标
icon_path = os.path.join(base_dir, "icon.ico")  # 替换为你的图标路径
if os.path.exists(icon_path):
    root.iconbitmap(icon_path)

# 创建样式
style = ttk.Style()
style.configure('Custom.TButton',
                background='#3498DB',  # 蓝色
                foreground='white',
                font=('微软雅黑', 12),
                padding=10)

# 创建标题
title_label = tk.Label(root, 
                      text=config['gui']['title'],
                      font=('微软雅黑', 18, 'bold'),
                      fg='white',
                      bg='#2C3E50')
title_label.pack(pady=20)

# 创建按钮框架
button_frame = tk.Frame(root, bg='#2C3E50')
button_frame.pack(expand=True)

# 自定义按钮样式
button_style = {
    'font': ('微软雅黑', 12),
    'width': 12,
    'height': 1,
    'bd': 0,
    'relief': 'flat',
    'cursor': 'hand2'
}

start_button = tk.Button(button_frame, 
                        text=config['gui']['start_text'],
                        command=start_recording,
                        bg='#2ECC71',  # 绿色
                        fg='white',
                        activebackground='#27AE60',
                        **button_style)
start_button.pack(side=tk.LEFT, padx=10)

stop_button = tk.Button(button_frame,
                       text=config['gui']['stop_text'],
                       command=stop_recording,
                       state=tk.DISABLED,
                       bg='#E74C3C',  # 红色
                       fg='white',
                       activebackground='#C0392B',
                       **button_style)
stop_button.pack(side=tk.LEFT, padx=10)

pause_button = tk.Button(button_frame,
                        text=config['gui']['pause_text'],
                        command=pause_recording,
                        state=tk.DISABLED,
                        bg='#F1C40F',  # 黄色
                        fg='white',
                        activebackground='#F39C12',
                        **button_style)
pause_button.pack(side=tk.LEFT, padx=10)

# 添加状态栏
status_frame = tk.Frame(root, bg='#34495E', height=30)
status_frame.pack(fill=tk.X, side=tk.BOTTOM)
status_label = tk.Label(status_frame,
                       text=config['gui']['ready_text'],
                       font=('微软雅黑', 10),
                       fg='white',
                       bg='#34495E')
status_label.pack(side=tk.LEFT, padx=10)

# 使窗口居中显示
root.update_idletasks()
width = root.winfo_width()
height = root.winfo_height()
x = screen_width - width - 50  # 距离右边50像素
y = screen_height - height - 100  # 距离底部100像素
root.geometry('{}x{}+{}+{}'.format(width, height, x, y))

root.mainloop()

# 释放音频资源
audio.terminate()